#include <vector>
#include "Barrage.h"
#include "Bullet.h"
#include "STGEngine.h"
#include "Clip.h"
#include <tolua++.h>
#include <lua.hpp>
#include <lualib.h>
#include <lauxlib.h>
#include <algorithm>
#include <boost/cast.hpp>
#include "Defs.h"
using namespace Ogre;

CBarrage::CBarrage()
: mBillboardSet(NULL)
, mPoolSize(0)
, mIsRendererConfigured(false)
, mDefaultWidth(100)
, mDefaultHeight(100)
, mLocalSpace(true)
, mSpeedFactor(1.0f)
, mIterationInterval(0)
, mIterationIntervalSet(false)
, mUpdateRemainTime(0)
, mIsEmitting(false)
, mBoundingRadius(0)
, mBoundsAutoUpdate(false)
, mBoundsUpdateTime(0)
//, mStopTime(0)
{
    // Create billboard set
    mBillboardSet = OGRE_NEW BillboardSet("", 0, true);
    // World-relative axes
    mBillboardSet->setBillboardsInWorldSpace(!mLocalSpace);
    mBillboardSet->setBillboardType(BBT_ORIENTED_COMMON);
    mBillboardSet->setCommonDirection(Vector3(0,1,0));
    mBillboardSet->setDefaultDimensions(100,100);

    setMaterialName( "BaseWhite" );

}

//CBarrage::CBarrage()
//: mBillboardSet(NULL)
//, mPoolSize(0)
//, mIsRendererConfigured(false)
//, mDefaultWidth(100)
//, mDefaultHeight(100)
//, mLocalSpace(true)
//, mSpeedFactor(1.0f)
//, mIterationInterval(0)
//, mIterationIntervalSet(false)
//, mUpdateRemainTime(0)
//, mIsEmitting(false)
//, mBoundingRadius(0)
//, mBoundsAutoUpdate(false)
//, mBoundsUpdateTime(0)
//{
//    // Create billboard set
//    mBillboardSet = OGRE_NEW BillboardSet("", 0, true);
//    // World-relative axes
//    mBillboardSet->setBillboardsInWorldSpace(!mLocalSpace);
//    mBillboardSet->setBillboardType(BBT_ORIENTED_COMMON);
//    mBillboardSet->setCommonDirection(Vector3(0,1,0));
//    mBillboardSet->setDefaultDimensions(100,100);
//}
//void CBarrage::initParameters(void)
//{
//    setParticleQuota(0);
//    setMaterialName()
//        dict->addParameter(ParameterDef("quota", 
//            "The maximum number of particle allowed at once in this system.",
//            PT_UNSIGNED_INT),
//            &msQuotaCmd);
//
//        dict->addParameter(ParameterDef("emit_emitter_quota", 
//            "The maximum number of emitters to be emitted at once in this system.",
//            PT_UNSIGNED_INT),
//            &msEmittedEmitterQuotaCmd);
//
//        dict->addParameter(ParameterDef("material", 
//            "The name of the material to be used to render all particles in this system.",
//            PT_STRING),
//            &msMaterialCmd);
//
//        dict->addParameter(ParameterDef("particle_width", 
//            "The width of particles in world units.",
//            PT_REAL),
//            &msWidthCmd);
//
//        dict->addParameter(ParameterDef("particle_height", 
//            "The height of particles in world units.",
//            PT_REAL),
//            &msHeightCmd);
//
//        dict->addParameter(ParameterDef("cull_each", 
//            "If true, each particle is culled in it's own right. If false, the entire system is culled as a whole.",
//            PT_BOOL),
//            &msCullCmd);
//
//        dict->addParameter(ParameterDef("renderer", 
//            "Sets the particle system renderer to use (default 'billboard').",
//            PT_STRING),
//            &msRendererCmd);
//
//        dict->addParameter(ParameterDef("sorted", 
//            "Sets whether particles should be sorted relative to the camera. ",
//            PT_BOOL),
//            &msSortedCmd);
//
//        dict->addParameter(ParameterDef("local_space", 
//            "Sets whether particles should be kept in local space rather than "
//            "emitted into world space. ",
//            PT_BOOL),
//            &msLocalSpaceCmd);
//
//        dict->addParameter(ParameterDef("iteration_interval", 
//            "Sets a fixed update interval for the system, or 0 for the frame rate. ",
//            PT_REAL),
//            &msIterationIntervalCmd);
//
//        dict->addParameter(ParameterDef("nonvisible_update_timeout", 
//            "Sets a timeout on updates to the system if the system is not visible "
//            "for the given number of seconds (0 to always update)",
//            PT_REAL),
//            &msNonvisibleTimeoutCmd);
//
//    }
//}
CBarrage::~CBarrage() {
    mBillboardSet->_notifyAttached(0);
    OGRE_DELETE  mBillboardSet;
}

//CBarrage* CBarrage::createBarrage(const Ogre::String &name) {
//    CBarrage *pBarrage =  static_cast<CBarrage*>( CEngine::getSingletonPtr()->getSceneManager()->
//        createMovableObject(name, CBarrageFactory::FACTORY_TYPE_NAME, NULL));
//
//    pBarrage->setMaterialName( "BaseWhite" );
//    pBarrage->setBulletQuota(100);
//    pBarrage->setBillboardSetBounds(AxisAlignedBox(-1000,-1000,-100,1000,1000,100));
//    pBarrage->configureRenderer();
//    return pBarrage;
//}

//------------------------------------------------------------------------------
CBullet* CBarrage::createBullet(void)
{
    CBullet* p = 0;
    if (mFreeBullets.empty()) {
       setBulletQuota(mPoolSize*2);
       configureRenderer();
    }
    if (!mFreeBullets.empty())
    {
        // Fast creation (don't use superclass since emitter will init)
        p = mFreeBullets.front();
        p->timeToLive = p->totalTimeToLive;
        p->position = 0;
        mActiveBullets.splice(mActiveBullets.end(), mFreeBullets, mFreeBullets.begin());
        
        //p->_notifyOwner(this);
    }
    assert(p);
    p->position = mParentPos;
    return p;
}
void CBarrage::setMaterialName(const String& name, const String& groupName )
{
    mMaterialName = name;
    mResourceGroupName = groupName;
    if (mIsRendererConfigured)
    {
        MaterialPtr mat = MaterialManager::getSingleton().load(
            mMaterialName, mResourceGroupName);
        mBillboardSet->setMaterialName(mMaterialName, mResourceGroupName);
    }
}
//-----------------------------------------------------------------------
CBullet* CBarrage::getBullet( int index )
{
    assert ( (boost::numeric_cast<size_t, int>(index) < mActiveBullets.size()) && "Index out of bounds!");
    ActiveBulletList::iterator i = mActiveBullets.begin();
    std::advance(i, index);
    return *i;
}

//-----------------------------------------------------------------------
size_t CBarrage::getBulletQuota(void) const
{
    return mPoolSize;
}
//-----------------------------------------------------------------------
void CBarrage::setBulletQuota( int size )
{
    //assert(size >=0 );
    // Never shrink below size()
    size_t currSize = mBulletPool.size();

    if( currSize < boost::numeric_cast<size_t, int>(size) )
    {
        // Will allocate particles on demand
        mPoolSize = size;

    }
}

//-----------------------------------------------------------------------
void CBarrage::fastForward(Real time, Real interval)
{
    // First make sure all transforms are up to date

    for (Real ftime = 0; ftime < time; ftime += interval)
    {
        _update(interval);
    }
}
//-----------------------------------------------------------------------
void CBarrage::increasePool(size_t size)
{
    size_t oldSize = mBulletPool.size();

    // Increase size
    mBulletPool.reserve(size);
    mBulletPool.resize(size);

    // Create new particles
    for( size_t i = oldSize; i < size; i++ )
    {
        mBulletPool[i] = new CBullet();
    }

    //if (mIsRendererConfigured)
    //{
    //    createVisualBullets(oldSize, size);
    //}

}
//-----------------------------------------------------------------------
void CBarrage::configureRenderer(void)
{
    // Actual allocate particles
    size_t currSize = mBulletPool.size();
    size_t size = mPoolSize;
    if( currSize < size )
    {
        this->increasePool(size);

        for( size_t i = currSize; i < size; ++i )
        {
            // Add new items to the queue
            mFreeBullets.push_back( mBulletPool[i] );
        }

        // Tell the renderer, if already configured
        mBillboardSet->setPoolSize(size);
    }

    if (/*mRenderer && */!mIsRendererConfigured)
    {
        mBillboardSet->setPoolSize(mBulletPool.size());
        mBillboardSet->_notifyAttached(mParentNode, mParentIsTagPoint);
        mBillboardSet->setDefaultDimensions(mDefaultWidth, mDefaultHeight);
        MaterialPtr mat = MaterialManager::getSingleton().load(
            mMaterialName, mResourceGroupName);
        mBillboardSet->setMaterialName(mMaterialName, mResourceGroupName);
        if (mRenderQueueIDSet) {
            assert(mRenderQueueID <= RENDER_QUEUE_MAX && "Render queue out of range!");
            mBillboardSet->setRenderQueueGroup(mRenderQueueID);
        }
        mBillboardSet->setBillboardsInWorldSpace(!mLocalSpace);
        mIsRendererConfigured = true;
    }
}
//-----------------------------------------------------------------------
void CBarrage::_notifyAttached(Node* parent, bool isTagPoint)
{
    MovableObject::_notifyAttached(parent, isTagPoint);
    if (mIsRendererConfigured)
    {
        mBillboardSet->_notifyAttached(parent, isTagPoint);
    }

    //if (parent && !mTimeController)
    //{
    //    //// Assume visible
    //    //mTimeSinceLastVisible = 0;
    //    //mLastVisibleFrame = Root::getSingleton().getNextFrameNumber();

    //    //// Create time controller when attached
    //    //ControllerManager& mgr = ControllerManager::getSingleton(); 
    //    //ControllerValueRealPtr updValue(OGRE_NEW CBarrageUpdateValue(this));
    //    //mTimeController = mgr.createFrameTimePassthroughController(updValue);
    //}
    //else if (!parent && mTimeController)
    //{
    //    //// Destroy controller
    //    //ControllerManager::getSingleton().destroyController(mTimeController);
    //    //mTimeController = 0;
    //}
}
void CBarrage::_notifyCurrentCamera(Camera* cam)
{
    MovableObject::_notifyCurrentCamera(cam);

    // Record visible
    if (isVisible())
    {			
        mLastVisibleFrame = Root::getSingleton().getNextFrameNumber();
        mTimeSinceLastVisible = 0.0f;

        //if (mSorted)
        //{
        //    _sortParticles(cam);
        //}

        //if (mRenderer)
        //{
            if (!mIsRendererConfigured)
                configureRenderer();

            mBillboardSet->_notifyCurrentCamera(cam);
        //}
    }
}
//-----------------------------------------------------------------------
void CBarrage::setBounds(const AxisAlignedBox& aabb)
{
    mAABB = aabb;
    mBoundingRadius = Math::boundingRadiusFromAABB(mAABB);

}
void CBarrage::setBillboardSetBounds(const Ogre::AxisAlignedBox& aabb) {
    setBounds(aabb);
    //if (mIsRendererConfigured) {
        mBillboardSet->setBounds(aabb, mBoundingRadius);
    //}
}

//-----------------------------------------------------------------------
Real CBarrage::msDefaultIterationInterval = 0;
void CBarrage::_update(Real timeElapsed)
{
    // Only update if attached to a node
    if (!mParentNode)
        return;

    // Scale incoming speed for the rest of the calculation
    timeElapsed *= mSpeedFactor;

    // Init renderer if not done already
    configureRenderer();

    //// Initialise emitted emitters list if not done already
    //initialiseEmittedEmitters();

    Real iterationInterval = mIterationIntervalSet ? 
        mIterationInterval : msDefaultIterationInterval;
    if (iterationInterval > 0)
    {
        mUpdateRemainTime += timeElapsed;

        while (mUpdateRemainTime >= iterationInterval)
        {
            // Update existing particles
            _expire(iterationInterval);
            //_triggerAffectors(iterationInterval);
            _applyMotion(iterationInterval);

            if(mIsEmitting)
            {
                // Emit new particles
                _triggerEmitters(iterationInterval);
            }

            mUpdateRemainTime -= iterationInterval;
        }
    }
    else
    {
        // Update existing particles
        _expire(timeElapsed);
        //_triggerAffectors(timeElapsed);
        _applyMotion(timeElapsed);

        if(mIsEmitting)
        {
            // Emit new particles
            _triggerEmitters(timeElapsed);
        }
    }

    //if (!mBoundsAutoUpdate && mBoundsUpdateTime > 0.0f)
    //    mBoundsUpdateTime -= timeElapsed; // count down 
    _updateBounds();

}
void CBarrage::_updateBounds() {

    if (mParentNode && (mBoundsAutoUpdate || mBoundsUpdateTime > 0.0f))
    {
        if (mActiveBullets.empty())
        {
            // No particles, reset to null if auto update bounds
            if (mBoundsAutoUpdate)
            {
                mWorldAABB.setNull();
            }
        }
        else
        {
            Vector3 min;
            Vector3 max;
            if (!mBoundsAutoUpdate && mWorldAABB.isFinite())
            {
                // We're on a limit, grow rather than reset each time
                // so that we pick up the worst case scenario
                min = mWorldAABB.getMinimum();
                max = mWorldAABB.getMaximum();
            }
            else
            {
                min.x = min.y = min.z = Math::POS_INFINITY;
                max.x = max.y = max.z = Math::NEG_INFINITY;
            }
            ActiveBulletList::iterator p;
            Vector3 halfScale = Vector3::UNIT_SCALE * 0.5;
            Vector3 defaultPadding = 
                halfScale * std::max(mDefaultHeight, mDefaultWidth);
            for (p = mActiveBullets.begin(); p != mActiveBullets.end(); ++p)
            {
                if ((*p)->mOwnDimensions)
                {
                    Vector3 padding = 
                        halfScale * std::max((*p)->mWidth, (*p)->mHeight);
                    min.makeFloor((*p)->position - padding);
                    max.makeCeil((*p)->position + padding);
                }
                else
                {
                    min.makeFloor((*p)->position - defaultPadding);
                    max.makeCeil((*p)->position + defaultPadding);
                }
            }
            mWorldAABB.setExtents(min, max);
        }


        if (mLocalSpace)
        {
            // Merge calculated box with current AABB to preserve any user-set AABB
            mAABB.merge(mWorldAABB);
        }
        else
        {
            // We've already put particles in world space to decouple them from the
            // node transform, so reverse transform back since we're expected to 
            // provide a local AABB
            AxisAlignedBox newAABB(mWorldAABB);
            newAABB.transformAffine(mParentNode->_getFullTransform().inverseAffine());

            // Merge calculated box with current AABB to preserve any user-set AABB
            mAABB.merge(newAABB);
        }

        mParentNode->needUpdate();
    }
}
//class CClip* CBarrage::createClip( const std::string &name )
//{
//    //lua_State *L = static_cast<CSTGEngine*>(CSTGEngine::getSingletonPtr())->lua();
//    //lua_getglobal(L, name.c_str());
//    //if (lua_isfunction(L, -1)) {
//    CClip *pClip = new CClip;
//    //pClip->
//    //pClip->load(filename, L);
//    mClips.insert(std::pair<std::string, CClip*>(name, pClip));   
//    return pClip;
//}

//------------------------------------------------------------------------------
void CBarrage::_triggerEmitters(Real timeElapsed)
{
    static lua_State *L = static_cast<CSTGEngine*>(CSTGEngine::getSingletonPtr())->lua();
    tolua_pushusertype(L, this, "CBarrage");
    lua_setglobal(L, "barrage");
    for (ClipMap::iterator iter = mClips.begin(); iter != mClips.end(); ++iter) {
        CClip *pClip = iter->second;
        pClip->update(timeElapsed);
    }
    //// Add up requests for emission
    //static vector<unsigned>::type requested;
    //if( requested.size() != mEmitters.size() )
    //    requested.resize( mEmitters.size() );

    //size_t totalRequested, emitterCount, i, emissionAllowed;
    //BulletEmitterList::iterator itEmit, iEmitEnd;
    //ActiveEmittedEmitterList::iterator itActiveEmit;
    //iEmitEnd = mEmitters.end();
    //emitterCount = mEmitters.size();
    //emissionAllowed = mFreeBullets.size();
    //totalRequested = 0;

    //// Count up total requested emissions for regular emitters (and exclude the ones that are used as
    //// a template for emitted emitters)
    //for (itEmit = mEmitters.begin(), i = 0; itEmit != iEmitEnd; ++itEmit, ++i)
    //{
    //    if (!(*itEmit)->isEmitted())
    //    {
    //        requested[i] = (*itEmit)->_getEmissionCount(timeElapsed);
    //        totalRequested += requested[i];
    //    }
    //}

    //// Add up total requested emissions for (active) emitted emitters
    //for (itActiveEmit = mActiveEmittedEmitters.begin(); itActiveEmit != mActiveEmittedEmitters.end(); ++itActiveEmit)
    //{
    //    totalRequested += (*itActiveEmit)->_getEmissionCount(timeElapsed);
    //}

    //// Check if the quota will be exceeded, if so reduce demand
    //Real ratio =  1.0f;
    //if (totalRequested > emissionAllowed)
    //{
    //    // Apportion down requested values to allotted values
    //    ratio =  (Real)emissionAllowed / (Real)totalRequested;
    //    for (i = 0; i < emitterCount; ++i)
    //    {
    //        requested[i] = static_cast<unsigned>(requested[i] * ratio);
    //    }
    //}

    //// Emit
    //// For each emission, apply a subset of the motion for the frame
    //// this ensures an even distribution of particles when many are
    //// emitted in a single frame
    //for (itEmit = mEmitters.begin(), i = 0; itEmit != iEmitEnd; ++itEmit, ++i)
    //{
    //    // Trigger the emitters, but exclude the emitters that are already in the emitted emitters list; 
    //    // they are handled in a separate loop
    //    if (!(*itEmit)->isEmitted())
    //        _executeTriggerEmitters (*itEmit, static_cast<unsigned>(requested[i]), timeElapsed);
    //}

    //// Do the same with all active emitted emitters
    //for (itActiveEmit = mActiveEmittedEmitters.begin(), i = 0; itActiveEmit != mActiveEmittedEmitters.end(); ++itActiveEmit, ++i)
    //    _executeTriggerEmitters (*itActiveEmit, static_cast<unsigned>((*itActiveEmit)->_getEmissionCount(timeElapsed) * ratio), timeElapsed);
}
//------------------------------------------------------------------------------    
void CBarrage::_applyMotion(Real timeElapsed)
{
    ActiveBulletList::iterator i, itEnd;
    CBullet* pBullet;
    //CBulletEmitter* pBulletEmitter;

    itEnd = mActiveBullets.end();
    for (i = mActiveBullets.begin(); i != itEnd; ++i)
    {
        pBullet = static_cast<CBullet*>(*i);
        //pBullet->position += (pBullet->direction * timeElapsed);
        pBullet->update(timeElapsed);

        if (pBullet->particleType == CBullet::Emitter)
        {
            // If it is an emitter, the emitter position must also be updated
            // Note, that position of the emitter becomes a position in worldspace if mLocalSpace is set 
            // to false (will this become a problem?)
            //pBulletEmitter = static_cast<BulletEmitter*>(*i);
            //pBullet->setPosition(pBullet->position);
        }
    }

    // Notify renderer
    //mRenderer->_notifyBulletMoved(mActiveBullets);
}
//-----------------------------------------------------------------------
void CBarrage::_expire(Real timeElapsed)
{
    ActiveBulletList::iterator i, itEnd;
    CBullet* pBullet;
    //CBulletEmitter* pBulletEmitter;

    itEnd = mActiveBullets.end();

    for (i = mActiveBullets.begin(); i != itEnd; )
    {
        pBullet = static_cast<CBullet*>(*i);
        if (pBullet->timeToLive < timeElapsed)
        {
            //// Notify renderer
            //mRenderer->_notifyBulletExpired(pBullet);

            // Identify the particle type
            if (pBullet->particleType == CBullet::Visual)
            {
                // Destroy this one
                mFreeBullets.splice(mFreeBullets.end(), mActiveBullets, i++);
            }
            else
            {
                //// For now, it can only be an emitted emitter
                //pBulletEmitter = static_cast<CBulletEmitter*>(*i);
                //list<CBulletEmitter*>::type* fee = findFreeEmittedEmitter(pBulletEmitter->getName());
                //fee->push_back(pBulletEmitter);

                //// Also erase from mActiveEmittedEmitters
                //removeFromActiveEmittedEmitters (pBulletEmitter);

                //// And erase from mActiveBullets
                //i = mActiveBullets.erase( i );
            }
        }
        else
        {
            // Decrement TTL
            pBullet->timeToLive -= timeElapsed;
            ++i;
        }

    }
}
//------------------------------------------------------------------------------
void CBarrage::_updateRenderQueue(RenderQueue* queue, 
    list<CBullet*>::type& currentBullets)
{
    //mBillboardSet->setCullIndividually(false);

    // Update billboard set geometry
    mBillboardSet->beginBillboards(currentBullets.size());
    Billboard bb;
    for (list<CBullet*>::type::iterator i = currentBullets.begin();
        i != currentBullets.end(); ++i)
    {
        CBullet* p = *i;
        bb.mPosition = p->position;
		if (mBillboardSet->getBillboardType() == BBT_ORIENTED_SELF ||
			mBillboardSet->getBillboardType() == BBT_PERPENDICULAR_SELF)
		{
			// Normalise direction vector
			bb.mDirection = p->direction;
			bb.mDirection.normalise();
		}
        bb.mColour = p->colour;
        bb.mRotation = p->rotation;
        // Assign and compare at the same time
        if ((bb.mOwnDimensions = p->mOwnDimensions) == true)
        {
            bb.mWidth = p->mWidth;
            bb.mHeight = p->mHeight;
        }
        mBillboardSet->injectBillboard(bb);

    }
    
    mBillboardSet->endBillboards();

    // Update the queue
    mBillboardSet->_updateRenderQueue(queue);
}
size_t CBarrage::getNumBullets(void) const
{
    return mActiveBullets.size();
}
const String& CBarrage::getMovableType(void) const
{
    return CBarrageFactory::FACTORY_TYPE_NAME;
}
//-----------------------------------------------------------------------
void CBarrage::setNonVisibleUpdateTimeout(Real timeout)
{
    //mNonvisibleTimeout = timeout;
    //mNonvisibleTimeoutSet = true;
}
//-----------------------------------------------------------------------
void CBarrage::setIterationInterval(Real interval)
{
    mIterationInterval = interval;
    mIterationIntervalSet = true;
}

void CBarrage::_updateRenderQueue(RenderQueue* queue) {
    _updateRenderQueue(queue, mActiveBullets);
}
void CBarrage::visitRenderables(Renderable::Visitor* visitor, 
                                                 bool debugRenderables)
{
    mBillboardSet->visitRenderables(visitor, debugRenderables);
}




//------------------------------------------------------------------------------
template<> CBarrageFactory* Singleton<CBarrageFactory>::ms_Singleton = 0;

String CBarrageFactory::FACTORY_TYPE_NAME = "Barrage";

//-----------------------------------------------------------------------
MovableObject* CBarrageFactory::createInstanceImpl( const String& name, 
                                                         const NameValuePairList* params)
{
    return OGRE_NEW CBarrage();
}
//-----------------------------------------------------------------------
const String& CBarrageFactory::getType(void) const
{
    return FACTORY_TYPE_NAME;
}
//-----------------------------------------------------------------------

void CBarrageFactory::destroyInstance( MovableObject* obj) 
{
    OGRE_DELETE obj;
}
//-----------------------------------------------------------------------